#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_console.h"
#include "argtable3/argtable3.h"


#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#include "wifi_app.h"
#include "ethernet_app.h"
#include "ppp_ifac.h"

#include "gw-device.h"

#include "internet_common.h"


/* FreeRTOS event group to signal when we are connected & ready to make a request */
EventGroupHandle_t wifi_event_group;
EventGroupHandle_t ethernet_event_group;


// const int IPV4_GOTIP_BIT = BIT0;
// const int IPV6_GOTIP_BIT = BIT1;

static const char *TAG = "NET-COMMON";

bool is_wifi_connected = false;
bool is_eth_connected = false;

net_info_t net_info = {0};

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    tcpip_adapter_ip_info_t ip;

    switch (event->event_id) {
    /******************************WIFI********************************/
    case SYSTEM_EVENT_STA_START:
        esp_wifi_connect();
        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
        break;
    case SYSTEM_EVENT_STA_CONNECTED:
        /* enable ipv6 */
        tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA);
        break;
    case SYSTEM_EVENT_STA_GOT_IP:
        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");

        memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t));
        ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip));
        ESP_LOGI(TAG, "wifi Got IP Addr");
        ESP_LOGI(TAG, "~~~~~~~~~~~");
        ESP_LOGI(TAG, "WIFIIP:" IPSTR, IP2STR(&ip.ip));
        ESP_LOGI(TAG, "WIFIMASK:" IPSTR, IP2STR(&ip.netmask));
        ESP_LOGI(TAG, "WIFIGW:" IPSTR, IP2STR(&ip.gw));
        ESP_LOGI(TAG, "~~~~~~~~~~~");

        net_info_t temp_wifi;
        temp_wifi.net_interface = NET_INTERFACE_WIFI;
        temp_wifi.ip = ip.ip;
        temp_wifi.gateway = ip.gw;
        temp_wifi.netmask = ip.netmask;
        set_net_info(&temp_wifi);


        is_wifi_connected = true;

        xEventGroupSetBits(wifi_event_group, IPV4_GOTIP_BIT);
        break;
    case SYSTEM_EVENT_STA_DISCONNECTED:

        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
        /* This is a workaround as ESP32 WiFi libs don't currently auto-reassociate. */
        esp_wifi_connect();

        is_wifi_connected = false;

        xEventGroupClearBits(wifi_event_group, IPV4_GOTIP_BIT);
        xEventGroupClearBits(wifi_event_group, IPV6_GOTIP_BIT);
        break;
    case SYSTEM_EVENT_AP_STA_GOT_IP6:
        ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP6");

        char *ip6 = ip6addr_ntoa(&event->event_info.got_ip6.ip6_info.ip);
        ESP_LOGI(TAG, "IPv6: %s", ip6);

        is_wifi_connected = true;

        xEventGroupSetBits(wifi_event_group, IPV6_GOTIP_BIT);
        break;
    
    /***********************ETH*************************************/
    case SYSTEM_EVENT_ETH_CONNECTED:
        ESP_LOGI(TAG, "Ethernet Link Up");
        break;
    case SYSTEM_EVENT_ETH_DISCONNECTED:
        ESP_LOGI(TAG, "Ethernet Link Down");

        is_eth_connected = false;

        xEventGroupClearBits(ethernet_event_group, IPV4_GOTIP_BIT);
        break;
    case SYSTEM_EVENT_ETH_START:
        ESP_LOGI(TAG, "Ethernet Started");
        break;
    case SYSTEM_EVENT_ETH_GOT_IP:
        memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t));
        ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(ESP_IF_ETH, &ip));
        ESP_LOGI(TAG, "Ethernet Got IP Addr");
        ESP_LOGI(TAG, "~~~~~~~~~~~");
        ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip.ip));
        ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip.netmask));
        ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip.gw));
        ESP_LOGI(TAG, "~~~~~~~~~~~");


        net_info_t temp_eth;
        temp_eth.net_interface = NET_INTERFACE_ETH;
        temp_eth.ip = ip.ip;
        temp_eth.gateway = ip.gw;
        temp_eth.netmask = ip.netmask;
        set_net_info(&temp_eth);

        is_eth_connected = true;

        xEventGroupSetBits(ethernet_event_group, IPV4_GOTIP_BIT);

        break;
    case SYSTEM_EVENT_ETH_STOP:
        ESP_LOGI(TAG, "Ethernet Stopped");
        break;
    default:
        break;
    }
    return ESP_OK;
}

void internet_common_init()
{
    tcpip_adapter_init();
    wifi_event_group = xEventGroupCreate();
    ethernet_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );

	gpio_pad_select_gpio(MODULE_RESET);
	/* Set the GPIO as a push/pull output */
	gpio_set_direction(MODULE_RESET, GPIO_MODE_OUTPUT);

	gpio_set_level(MODULE_RESET, 1);
}

bool isWIfiConnected()
{
    return is_wifi_connected;
}

bool isEthernetConnected()
{
    return is_eth_connected;
}

#if (CONFIG_NET_CNT_PRI_SORT)
//false:无需重新存储联网优先级
//true:需要重新存储联网优先级
bool sort_network_connecting_priority(config_para_t* device_settings,char c,char priority_string[])
{
    memcpy(priority_string,device_settings->network_connecting_priority,sizeof(device_settings->network_connecting_priority)); 

    if(priority_string[0] == c)
    {
        return false;
    }
    else if(priority_string[1] == c)
    {
        priority_string[1] = priority_string[0];
        priority_string[0] = c;
    }
    else if(priority_string[2] == c)
    {
        priority_string[2] = priority_string[1];        
        priority_string[1] = priority_string[0];
        priority_string[0] = c;
    }

    return true;
}
#endif


void set_net_info(net_info_t* src)
{
    net_info = *src;
}

void get_net_info(net_info_t* dest)
{
    *dest = net_info;
}

void reset_net_info()
{
    memset(&net_info,0,sizeof(net_info));
}

uint8_t get_interface_type()
{
    return net_info.net_interface;
}

void printf_net_info()
{
    printf("****************IP INFO************************\n");
    switch (net_info.net_interface)
    {
        case NET_INTERFACE_WIFI:
        {
            wifi_ap_record_t ap_info;
            esp_wifi_sta_get_ap_info(&ap_info);
            printf("\tType:\t\tWIFI \tRSSI:%d\n",ap_info.rssi);
        }
            break;
        
        case NET_INTERFACE_ETH:
            printf("\tType:\t\tEthernet \n");
            break;
        
        case NET_INTERFACE_4G:
            printf("\tType:\t\t4G/3G/2G \n");
            break;            
        
        default:
            printf("\tType:\t\tError \n");
            break;
    }

    //IPSTR, IP2STR(&ip.ip)
    printf("\tIP:\t\t%d.%d.%d.%d\n",IP2STR(&net_info.ip));
    printf("\tGateway:\t%d.%d.%d.%d\n",IP2STR(&net_info.gateway));
    printf("\tNetmask:\t%d.%d.%d.%d\n",IP2STR(&net_info.netmask));
}

bool connect_without_init(config_para_t* device_settings)
{
    bool internet_connected = false;//标志是否连接成功
    bool connec_running = false;//标志是否进行连接

    connec_running = true;
    char* c = device_settings->network_connecting_priority;
    while(internet_connected == false && connec_running == true)
    {
        switch (*c)
        {
            case 'w':
                if(wifi_init(&(device_settings->wifi_info)) == false)
                {
                    printf("wifi initialization failed...\n");
                    //wifi 连接失败
                    ESP_LOGI("NET-INIT","wifi connect failed,ssid:%s,password:%s",(char*)device_settings->wifi_info.ssid,(char*)device_settings->wifi_info.password);
                
                    esp_wifi_disconnect();
                    esp_wifi_stop();
                    c++;
                }    
                else
                {
                    printf("wifi initialization success...\n");                
                    internet_connected = true;
                }
                break;
            
            case 'e':
                if(ethernet_init(NULL) == false)
                {
                    printf("ethernet initialization failed...\n");
                    //ethernet 连接失败
                    ESP_LOGI("NET-INIT","ethernet connect failed");  

                    esp_eth_disable();

                    c++;
                }
                else 
                {
                    printf("ethernet initialization success...\n");        
                    internet_connected = true;
                }
                break;
            
            case 'g':
                //PWRKEY直接接地，上电自启动
                if(ppposInit(NULL) == false)
                {
                    printf("4G/3G/2G initialization failed...\n");
                    //PPPOS 连接失败
                    ESP_LOGI("NET-INIT","PPPOS connect failed"); 

                    ppposStop();
                
                    c++;
                }
                else
                {
                    printf("4G/3G/2G initialization success...\n");        
                    internet_connected = true;
                }

                break;
        
            default:
                internet_connected = false;
                connec_running = false;
                break;
        }
    }
    
    if(internet_connected == false)
    {
        printf("connect to internet fail...\n");
        return false;
    }
    else
    {
        printf("connect to internet success...\n");

    #if (CONFIG_NET_CNT_PRI_SORT)
        char priority_save_string[4] = {0};

        if(sort_network_connecting_priority(device_settings,*c,priority_save_string) == true)
        {
            config_para_t* temp_para = (config_para_t*)malloc(sizeof(config_para_t));

            get_device_settings(temp_para);

            memcpy(temp_para->network_connecting_priority,priority_save_string,4);

            set_device_settings(temp_para);
            save_device_settings();

            ESP_LOGI("BRD-INIT","now network connecting priority:%s",temp_para->network_connecting_priority);
        }
    #endif

        return true;
    }
}

bool connect_to_network(config_para_t* device_settings)
{
    internet_common_init();

    return connect_without_init(device_settings);
}

// bool reconnect_to_network(config_para_t* device_settings)
// {
//     if(net_info.net_interface == NET_INTERFACE_4G)
//         ppposStop();
    
//     vTaskDelay(500);

//     return connect_without_init(device_settings);
// }
